package top.yangguangmc.sunshine_anticheat.check.checks.player;

import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerRotation;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import top.yangguangmc.sunshine_anticheat.check.Check;
import top.yangguangmc.sunshine_anticheat.check.CheckCategory;
import top.yangguangmc.sunshine_anticheat.check.CheckSetting;
import top.yangguangmc.sunshine_anticheat.events.ServerTickEvent;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import static top.yangguangmc.sunshine_anticheat.utils.Builtin.abs;

public class DeathActionCheck extends Check {
    private final Map<UUID, Long> deathTimes = new HashMap<>();
    //    private final Map<UUID, Location> lastLocation = new HashMap<>();
    private final Map<UUID, Rotation> lastRotation = new HashMap<>();
    @CheckSetting(configName = "delay-after-death")
    public int delay = 1;

    public DeathActionCheck() {
        super("DeathAction", "death-action", CheckCategory.PLAYER);
    }

    @EventHandler
    public void onDeath(PlayerDeathEvent event) {
        if (!validateCheckable(event.getEntity())) return;
        deathTimes.put(event.getEntity().getUniqueId(), ServerTickEvent.tickId);
    }

    @EventHandler
    public void onInteract(PlayerInteractEvent event) {
        Player player = event.getPlayer();
        if (!validateCheckable(player)) return;
        if (event.getAction() != Action.PHYSICAL && isPlayerDead(player)) {
            long timeAfterDeath = getTimeAfterDeath(player);
            failCheck(player, timeAfterDeath > 10 ? 3 : 1, "Tried to interact after died. Delta: {} ticks", timeAfterDeath);
        }
    }

    @EventHandler
    public void onDamageEntity(EntityDamageByEntityEvent event) {
        if (!validateCheckable(event.getDamager())) return;
        Player player = (Player) event.getDamager();
        if (isPlayerDead(player)) {
            long timeAfterDeath = getTimeAfterDeath(player);
            failCheck(player, timeAfterDeath > 10 ? 3 : 1, "Tried to attack after died. Delta: {} ticks", timeAfterDeath);
        }
    }

    @EventHandler
    public void onPlace(BlockPlaceEvent event) {
        Player player = event.getPlayer();
        if (!validateCheckable(player)) return;
        if (isPlayerDead(player)) {
            long timeAfterDeath = getTimeAfterDeath(player);
            failCheck(player, timeAfterDeath > 10 ? 3 : 1, "Tried to place blocks after died. Delta: {} ticks", timeAfterDeath);
        }
    }

    @EventHandler
    public void onBreak(BlockBreakEvent event) {
        Player player = event.getPlayer();
        if (!validateCheckable(player)) return;
        if (isPlayerDead(player)) {
            long timeAfterDeath = getTimeAfterDeath(player);
            failCheck(player, timeAfterDeath > 10 ? 3 : 2, "Tried to break blocks after died. Delta: {} ticks", timeAfterDeath);
        }
    }

    @Override
    public void onPacketReceive(PacketReceiveEvent event) {
        Player player = (Player) event.getPlayer();
        if (!validateCheckable(player)) return;
//        if (event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION || event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION) {
//            WrapperPlayClientPlayerPosition packet = new WrapperPlayClientPlayerPosition(event);
//            Location loc = new Location(player.getWorld(), packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ());
//            if (isPlayerDead(player)) {
//                Location last = lastLocation.get(player.getUniqueId());
//                if (last == null) return;
//                if (abs(loc.getX() - last.getX()) > 0.1 || abs(loc.getZ() - last.getZ()) > 0.1) {
//                    long timeAfterDeath = getTimeAfterDeath(player);
//                    failCheck(player, timeAfterDeath > 10 ? 3 : 1, "Tried to move after died. Delta: {} ticks", timeAfterDeath);
//                }
//            }
//            lastLocation.put(player.getUniqueId(), loc);
//        } //TODO: 误报严重
        if (event.getPacketType() == PacketType.Play.Client.PLAYER_ROTATION || event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION) {
            WrapperPlayClientPlayerRotation packet = new WrapperPlayClientPlayerRotation(event);
            Rotation rotation = new Rotation(packet.getYaw(), packet.getPitch());
            if (isPlayerDead(player) && !rotation.approxEquals(lastRotation.get(player.getUniqueId()), 1)) {
                long timeAfterDeath = getTimeAfterDeath(player);
                failCheck(player, timeAfterDeath > 10 ? 3 : 2, "Tried to rotate after died. Delta: {} ticks", timeAfterDeath);
            }
        }
    }

    @Override
    public void onPacketSend(PacketSendEvent event) {
        if (event.getPacketType() == PacketType.Play.Server.PLAYER_POSITION_AND_LOOK) {
            Player player = (Player) event.getPlayer();
            if (!validateCheckable(player)) return;
//            lastLocation.remove(player.getUniqueId());
            lastRotation.remove(player.getUniqueId());
        }
    }

    private boolean isPlayerDead(Player player) {
        return player.isDead() && getTimeAfterDeath(player) > delay;
    }

    private long getTimeAfterDeath(Player player) {
        return ServerTickEvent.tickId - deathTimes.getOrDefault(player.getUniqueId(), 0L);
    }

    static class Rotation {
        float yaw;
        float pitch;

        public Rotation(float yaw, float pitch) {
            this.yaw = yaw;
            this.pitch = pitch;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Rotation rotation = (Rotation) o;
            return Float.compare(yaw, rotation.yaw) == 0 && Float.compare(pitch, rotation.pitch) == 0;
        }

        public boolean approxEquals(Rotation other, float diff) {
            if (other == null) return false;
            return abs(yaw - other.yaw) <= diff && abs(pitch - other.pitch) <= diff;
        }

        @Override
        public int hashCode() {
            return Objects.hash(yaw, pitch);
        }
    }
}
